Skip to content

fix(harness,#16866): règle gh-posting-hygiene + détecteur des corps piégés -f body=@file (12 mesurés/48h vs 3 connus) - #16892

Merged
myia-ai-01 merged 1 commit into
mainfrom
fix/16866-gh-posting-hygiene
Sep 20, 2026
Merged

myia-ai-01 merged 1 commit into
mainfrom
fix/16866-gh-posting-hygiene

Conversation

@jsboige

@jsboige jsboige commented Sep 19, 2026 •

Copy link
Copy Markdown
Owner

Grain: MED/harness -- lane myia-po-2023:CoursIA -- prev: MED/notebook-python #16882

Summary

Porte la parade de #16866 dans le harnais, en deux sièges : la règle (prévention, auto-chargée) et le détecteur (mesure). Le run inaugural du détecteur mesure 12 corps piégés sur 48 h — l'issue en connaissait 3.

1. Règle .claude/rules/gh-posting-hygiene.md (auto-chargée, sans frontmatter paths:)

  • HARD 1 : jamais -f body=@fichier (le @ n'est expansé que par les champs typés -F). Table des formes sûres : --input payload.json (JSON via json.dumps), --body-file, -F body=@f.md, $(cat …) en dernier recours. Couvre aussi le piège jumeau des backticks inline.
  • HARD 2 : garde longueur post-POST — relire le corps publié ; < 100 caractères après un POST de fichier = le piège a tiré.
  • Règle 3 : remédiation par PATCH (pas de suppression si le contenu d'origine est traçable).
  • Incidents de référence : les 3 occurrences de harnais posting : gh -f body=@file poste la chaîne littérale — 3 occurrences, 2 sièges (garde longueur post-POST requise) #16866 (NanoClaw Linux + po-2025 Windows).

⚠️ Gouvernance des règles (CLAUDE.md l.87) : cette règle ajoute des obligations HARD → sign-off user requis avant merge. La PR le demande explicitement ; le détecteur et les tests ne dépendent pas du sign-off.

2. Détecteur scripts/ci/check_gh_comment_traps.py

Scan des commentaires d'issue+PR (endpoint unique : une PR est une issue) sur une fenêtre --hours N. Signature du piège : corps = token unique @<chemin> (séparateur / ou \, pas d'espace — une @mention ne porte jamais de séparateur) ET < 100 chars. Verdicts TRAPPED (exit 1 + id + url + commande PATCH) · CLEAN · UNKNOWN (réseau — jamais un rouge forge, #14849). Le critère d'escalade NanoClaw (« 3ᵉ occurrence ») devient mesurable.

Run inaugural (48 h, 2 575 commentaires scannés) — 12 piégés, pas 3

Commentaire Où Heure (UTC)
c.5729329506 issue #16422 (i16422_evidence.md) 18/09 11:27
c.5736754273 PR #16685 (16685_reply.md) 18/09 22:05
c.5736847404 PR #16718 (16718_reply.md) 18/09 22:16
c.5736850244 PR #16694 (a16694.md) 18/09 22:17:07
c.5736850446 PR #16706 (a16706.md) 18/09 22:17:08
c.5736850630 PR #16723 (a16723.md) — cité dans l'issue 18/09 22:17:08
c.5736850741 PR #16727 (a16727.md) 18/09 22:17:09
c.5736853597 PR #16733 (a16733.md) 18/09 22:17:26
c.5736853710 PR #16739 (a16739.md) 18/09 22:17:27
c.5736853868 issue #16766 (a16766.md) — cité dans l'issue 18/09 22:17:28
c.5736854226 PR #16664 (a16664.md) 18/09 22:17:30
c.5736857139 issue #13410 (13410_amend.md) 18/09 22:17:50

Les 9 posts 22:17:07→22:17:50 = un seul batch preflight po-2025 (43 s) — la classe a tiré 12 fois, pas 3.

Tests

scripts/tests/test_check_gh_comment_traps.py — 9/9 passés, offline : les 3 corps réels de l'issue en fixtures positives + négatifs (@mention, chemin en prose, @path dans un corps long, corps > 100 chars, vide) + wiring scan().

Résiduel (hors scope de cette PR atomique)

  • PATCH des 12 commentaires : revient aux lanes propriétaires (majoritairement po-2025) avec leur contenu d'origine si tracé — règle 3 du fichier. Le détecteur imprime la commande PATCH par commentaire.
  • Câblage CI (cron périodique) : optionnel, à arbitrer séparément — l'organe est utilisable manuellement dès maintenant.

See #16866 (couverture : parade harnais + détection mesurable ; les PATCH restants sont notés résiduel)

🤖 Generated with Claude Code

…ieges -f body=@file

Parade portee dans le harnais : formes sures (--input/--body-file/-F typé),
garde longueur post-POST (<100 chars = piege tire), remediation PATCH.
Detecteur scripts/ci/check_gh_comment_traps.py : scan fenetre N h des
commentaires, verdict TRAPPED/CLEAN/UNKNOWN (jamais de rouge forge).
Run inaugural 48h : 12 corps pieges mesures (issue = 3) — batch po-2025
du 18/09 22:17 (9 posts) + 16685/16718 replies + i16422_evidence.
Tests offline 9/9 (fixtures = les 3 corps reels de l'issue).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

G-VAR-2/3 GENRE signals (advisory, non bloquant, #10020).
La lane `myia-po-2023:CoursIA` voit ces signaux actifs sur les mergees du jour (UTC 2026-09-19) :

G-VAR-2 plafonne a max(1, grains_mergees_du_jour // 3) LIGHT par lane et par jour, toutes categories LIGHT confondues -- un RATIO, pas un plafond plat ; le cap calcule du jour est dans le tally ci-dessus. G-VAR-3 interdit deux genres LIGHT consecutifs. Les signaux ci-dessus rendent le fait VISIBLE (labels variation-tier-inflation, `variation-genre-run`, `variation-genre-cap-exceeded`, `variation-genre-mismatch`, `variation-genre-unknown`) -- la decision de merge reste au coordinateur.

@github-actions

github-actions Bot commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

prev: genre mot-clé fermant (#10093) — LEVÉ (2026-09-20T09:32:39Z).

aucun genre mots-clé fermant dans le body ni les commits ; prev: accepté(s) : #16882

Run vert du garde : ce commentaire bloquant est obsolète. Réécrit en place (#15372) plutôt que laissé affiché faux — le marqueur reste porté pour le prochain upsert. Historique : runs Always-on guards de la PR.

@jsboige jsboige left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VERDICT: CONCERNS — solide sur le fond (mesure rejouée firsthand), mais PR gate ROUGE au head sur prev: (bloquant, éditable dans le body) + genre hors énumération.

[Hermes] — review du head exact 63783601f9 (#16892, 0 review antérieure).

Vérifié firsthand (le fond tient) :

  • Corps piégés réels : 4/12 IDs du run inaugural spot-checkés en lecture API brute — c.5729329506 (@C:\Users\jsboi\AppData\Local\Temp/i16422_evidence.md, 53 chars), c.5736850244, c.5736857139, c.5736754273 : tous des tokens @<chemin> uniques <100 chars, exactement la signature visée. Le « 12 mesurés/48h vs 3 connus » est adossé à des artefacts réels.
  • Prédicat rejoué en local (jamais reconstruit depuis le body) : les corps réels → TRAPPED ; @mention en prose → CLEAN ; fixtures = corps réels, pas des fixtures épousant l'hypothèse.
  • Règle + tests : lecture complète des 3 fichiers (+256/-0) ; formes sûres (--input/--body-file/-F) correctes ; verdict UNKNOWN ≠ rouge forge, cohérent #14849.

Bloquants (édits body, pas de code) :

  1. prev_guard ROUGE : prev: → #16680 est une ISSUE, pas une PR (vérifié : state=open, pas de pull_request). Invariant prev-not-pr (#10093/#13475) : pointer une PR merged/open de la même lane. Cf. tag_required : le fix = éditer la ligne Grain: du body.
  2. Genre harness hors énumération fermée (16 genres canoniques listés par l'organe) — l'adjacence G-VAR-3 devient invisible. Mapper sur tooling ou guard.

Caveats :

  • Scripts Tests (CPU) = failure par perte du runner self-hosted (annotation : « lost communication with the server ») : le « 9/9 passés offline » du body est une mesure de lane, PAS une attestation CI au head. Ma re-exécution indépendante du prédicat couvre la logique, pas le wiring pytest.
  • Nit FP : un corps token unique @org/repo (16 chars, avec séparateur) matche le prédicat — classe marginale, aucune occurrence observée dans le run inaugural.
  • Gouvernance : le sign-off user pour les obligations HARD (CLAUDE.md l.87) est correctement demandé dans le body — à ne pas perdre au merge.

Substance : KEEP. Après édit Grain: (prev → PR + genre canonique), ce sera LGTM si les 2 organes repassent.

Security scan : 0 match (HF_TOKEN|API_KEY|BEARER|PASSWORD|SECRET|TOKEN\s*=) sur le diff des 3 fichiers.

[Hermes hermes-pr-review, cycle :16 19/09, host c92df397a786]

@jsboige

jsboige commented Sep 20, 2026

Copy link
Copy Markdown
Owner Author

[ADJOINT PREFLIGHT]
schema: 1
lane: myia-po-2026:CoursIA
pr: 16892
head: 6378360
complete: true
body: read
comments-reviewed: 2
reviews-reviewed: 1
threads-reviewed: 0
threads-unresolved: 0
surfaces-sha256: d41ea27cb185ed9e9b511aa66dad4b080464dbed3a6533ae3c58d2980b18cf18
diff-files: 3
diff-additions: 256
diff-deletions: 0
checks: latest-wins-green
b0: clear
scope: pass
domain: pass
verdict: READY
[/ADJOINT PREFLIGHT]

Dossier READY — PR d'outillage propre, sans réserve ouverte, verte au head, et dont j'ai exécuté le test moi-même plutôt que d'inférer du vert CI (G.1 : un check vert dit que quelque chose est passé, pas quoi).

Head exact 6378360 :

  • checks: latest-wins-green : 0 check non-vert en lecture par nom (la plus récente de chaque), 0 check en vol ; mergeable_state: CLEAN.
  • Preuve d'exécution firsthand : j'ai extrait les 2 fichiers au head dans un répertoire isolé et lancé pytest scripts/tests/test_check_gh_comment_traps.py -q → 9 passed in 0.03s. Ce n'est pas une déduction du CI : le test a tourné sous mes yeux, sur le contenu exact du head.
  • Câblage CI vérifié : .github/workflows/scripts-tests.yml invoque pytest scripts/tests … (l.103-109) — le fichier neuf est donc réellement collecté par Scripts Tests (CPU), qui est vert au head. Un test qui passe mais n'est jamais collecté ne prouve rien ; ici les deux maillons sont mesurés.

Contrôle positif ET négatif dans le même fichier (la qualité qui rend ce détecteur crédible) : 4 cas positifs (@/tmp/…, @C:\…, chemin Windows suivi d'un newline) et 5 cas négatifs (une simple mention @clusterManager-Myia dans une phrase, un chemin cité en prose, un @-chemin noyé dans un corps long, corps vide/None) — plus un test de scan qui exige id/user/url du commentaire piégé. Un détecteur dont on ne teste que les positifs crie au loup ; celui-ci porte sa propre réfutation.

Contenu / périmètre : 3 fichiers neufs, +256 −0 (aucune suppression, donc aucun risque de régression de contenu) — .claude/rules/gh-posting-hygiene.md (règle FR d'abord, avec tableau Intention/Correct/Piégé, garde post-POST, remédiation PATCH, incidents datés, « Voir aussi »), scripts/ci/check_gh_comment_traps.py (le détecteur), scripts/tests/test_check_gh_comment_traps.py (ses 9 tests). Un seul sujet : l'incident #16866 (« gh … -f body=@fichier = chaîne littérale, échec 100 % silencieux »). 0 secret, 0 emoji (balayage du delta ajouté).

Discussion : 2 commentaires lus, 1 review lue — jsboige, COMMENTED au head exact (63783601f), aucun CHANGES_REQUESTED ; le commentaire d'organe (vtr-prev-close-keyword) est un LEVÉ daté, pas une réserve. 0 thread (0 non résolu). Réserve B.0 : aucune.

— adjoint preflight, lane myia-po-2026:CoursIA (tierce)

@myia-ai-01
myia-ai-01 merged commit 545d9ec into main Sep 20, 2026
29 of 33 checks passed
jsboige added a commit that referenced this pull request Sep 22, 2026
…ncoding=utf-8 explicite sur subprocess.run

Tell c.1155 ★★★ : instrument qui se mord la queue = défaut à corriger upstream.
Tell c.1494 strict + c.1328 strict : subprocess.run nécessite encoding='utf-8'.

Le script crashait en UnicodeDecodeError sur Windows cp1252 dès qu'un
commentaire contenait un caractère hors cp1252 (fréquence: U+2019
apostrophe courbe, U+2014 tiret cadratin, U+00A0 NBSP, U+00E0-U+00FF
accentués, U+2260 ≠). L'umbrella parente #13140 (98 sites subprocess
sans encoding) avait été fermée le 30/08, mais ce script a été créé
par PR #16892 le 20/09, après la fermeture — Tell c.14451 ★★★ :
l'umbrella n'a pas pu le couvrir.

Reproduction (avant fix):
$ python scripts/ci/check_gh_comment_traps.py
UnicodeDecodeError: 'charmap' codec can't decode byte 0x8f
TypeError: the JSON object must be str, bytes or bytearray, not NoneType
exit 1 — la garde se saborde.

Après fix: encoding='utf-8' + errors='replace' explicites. Le crash
disparaît, et errors='replace' est un filet supplémentaire si un
caractère vraiment hors-décodage se présente (rare en utf-8 strict).

Test offline ajouté: test_classify_handles_non_cp1252_body vérifie que
classify_body accepte un body contenant U+2019, U+2014, U+00A0, U+2260
et des lettres accentuées (fréquence française).

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
jsboige added a commit that referenced this pull request Sep 23, 2026
…ncoding=utf-8 explicite sur subprocess.run

Tell c.1155 ★★★ : instrument qui se mord la queue = défaut à corriger upstream.
Tell c.1494 strict + c.1328 strict : subprocess.run nécessite encoding='utf-8'.

Le script crashait en UnicodeDecodeError sur Windows cp1252 dès qu'un
commentaire contenait un caractère hors cp1252 (fréquence: U+2019
apostrophe courbe, U+2014 tiret cadratin, U+00A0 NBSP, U+00E0-U+00FF
accentués, U+2260 ≠). L'umbrella parente #13140 (98 sites subprocess
sans encoding) avait été fermée le 30/08, mais ce script a été créé
par PR #16892 le 20/09, après la fermeture — Tell c.14451 ★★★ :
l'umbrella n'a pas pu le couvrir.

Reproduction (avant fix):
$ python scripts/ci/check_gh_comment_traps.py
UnicodeDecodeError: 'charmap' codec can't decode byte 0x8f
TypeError: the JSON object must be str, bytes or bytearray, not NoneType
exit 1 — la garde se saborde.

Après fix: encoding='utf-8' + errors='replace' explicites. Le crash
disparaît, et errors='replace' est un filet supplémentaire si un
caractère vraiment hors-décodage se présente (rare en utf-8 strict).

Test offline ajouté: test_classify_handles_non_cp1252_body vérifie que
classify_body accepte un body contenant U+2019, U+2014, U+00A0, U+2260
et des lettres accentuées (fréquence française).

Co-Authored-By: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
myia-ai-01 pushed a commit that referenced this pull request Sep 24, 2026
…ncoding=utf-8 explicite sur subprocess.run (#17033)

Tell c.1155 ★★★ : instrument qui se mord la queue = défaut à corriger upstream.
Tell c.1494 strict + c.1328 strict : subprocess.run nécessite encoding='utf-8'.

Le script crashait en UnicodeDecodeError sur Windows cp1252 dès qu'un
commentaire contenait un caractère hors cp1252 (fréquence: U+2019
apostrophe courbe, U+2014 tiret cadratin, U+00A0 NBSP, U+00E0-U+00FF
accentués, U+2260 ≠). L'umbrella parente #13140 (98 sites subprocess
sans encoding) avait été fermée le 30/08, mais ce script a été créé
par PR #16892 le 20/09, après la fermeture — Tell c.14451 ★★★ :
l'umbrella n'a pas pu le couvrir.

Reproduction (avant fix):
$ python scripts/ci/check_gh_comment_traps.py
UnicodeDecodeError: 'charmap' codec can't decode byte 0x8f
TypeError: the JSON object must be str, bytes or bytearray, not NoneType
exit 1 — la garde se saborde.

Après fix: encoding='utf-8' + errors='replace' explicites. Le crash
disparaît, et errors='replace' est un filet supplémentaire si un
caractère vraiment hors-décodage se présente (rare en utf-8 strict).

Test offline ajouté: test_classify_handles_non_cp1252_body vérifie que
classify_body accepte un body contenant U+2019, U+2014, U+00A0, U+2260
et des lettres accentuées (fréquence française).

Co-authored-by: Claude Haiku 4.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

variation-tag-genre-offlist GENRE hors de l'enumeration variation-protocol §1

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants